﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;
using NGridDrawer.Partitions;
using NGridDrawer.Report.Layout;

namespace NGridDrawer.Report
{
    /// <LastUpdate author="Blaise Braye" Date="30/06/2008 10:00"/>
    /// <summary>Provides report drawing feature (decorate <see cref="IPartitionDrawer"/> instances)
    /// </summary>
    public class ReportDrawer : IReportDrawer
    {
        private IPartitionDrawer _drawer;

        /// <summary>
        /// 	<para>Initializes an instance of the <see cref="ReportDrawer"/> class.</para>
        /// </summary>
        /// <param name="drawer">This is the drawer which will be used to paint the report
        /// </param>
        public ReportDrawer(IPartitionDrawer drawer)
        {
            GridPadding = Padding.Empty;
            Drawer = drawer;
        }

        private RectangleF[] sessionClipBounds { get; set; }

        private Dictionary<TemplateCode, string> sessionCodes { get; set; }

        /// <summary>Get/Set PartitionDrawer Property</summary>
        protected PartitionDrawer PartitionDrawer { get; private set; }

        /// <summary>Get IsInitialized Property</summary>
        public bool IsInitialized
        {
            get { return PartitionDrawer != null && PartitionDrawer.IsInitialized; }
        }

        #region IReportDrawer Members

        /// <summary>Get/Set Component Property</summary>
        public IPartitionDrawer Drawer
        {
            get { return _drawer; }
            set
            {
                _drawer = value;
                PartitionDrawer = new PartitionDrawer(value);
            }
        }

        /// <summary>Get/Set Title Property</summary>
        public IBlockTitle Title { get; set; }

        /// <summary>Get/Set Header Property</summary>
        public IBlock Header { get; set; }

        /// <summary>Get/Set Footer Property</summary>
        public IBlock Footer { get; set; }


        /// <summary>Get/Set GridPadding Property</summary>
        public Padding GridPadding { get; set; }


        /// <summary>
        /// Draw all elements of a <see cref="PartitionGrid"/> in a custom report
        /// </summary>
        /// <param name="graphic">graphics used to draw</param>
        /// <param name="clipBounds">Clip area of the report</param>
        /// <returns>
        /// Component has more pages to draw?
        /// </returns>
        public bool Draw(Graphics graphic, RectangleF clipBounds)
        {
            if (!IsInitialized)
            {
                RectangleF firstLevelBounds = adjustPartitionBoundsWithMe(graphic, clipBounds, true);
                RectangleF nextLevelBounds = adjustPartitionBoundsWithMe(graphic, clipBounds, false);

                sessionClipBounds = new[] {firstLevelBounds, nextLevelBounds};

                var clipHeights = new[] {firstLevelBounds.Height, nextLevelBounds.Height};

                var clipWidths =
                    new[] {clipBounds.Width};


                PartitionDrawer.InitializeSession(graphic, clipHeights, clipWidths);
                PrepareSessionCodes();
            }


            sessionCodes[TemplateCode.SheetNumber] =
                (PartitionDrawer.Current.Ordinal + 1).ToString(CultureInfo.CurrentCulture);


            var blockClipBounds = new RectangleF(clipBounds.Location, clipBounds.Size);


            if (Header != null)
            {
                blockClipBounds.Height = Header.GetSize(graphic, clipBounds.Size).Height;
                Header.Draw(graphic, sessionCodes, blockClipBounds);
            }
            

            if (PartitionDrawer.Current.Ordinal == 0 && Title != null)
            {
                blockClipBounds.Y += blockClipBounds.Height;
                blockClipBounds.Height = Title.GetSize(graphic, clipBounds.Size).Height;

                Title.Draw(graphic, sessionCodes, blockClipBounds);
            }


            RectangleF currentClipBounds =
                (PartitionDrawer.Current.LevelId < sessionClipBounds.Length)
                    ? sessionClipBounds[PartitionDrawer.Current.LevelId]
                    : sessionClipBounds[sessionClipBounds.Length - 1];

            blockClipBounds.Y += PartitionDrawer.Current.Level.Height;

            bool hasMorePages =
                PartitionDrawer.Paint(graphic, currentClipBounds);

            if (Footer != null)
            {
                blockClipBounds.Height = Footer.GetSize(graphic, clipBounds.Size).Height;
                blockClipBounds.Y = clipBounds.Bottom - blockClipBounds.Height;

                Footer.Draw(graphic, sessionCodes, blockClipBounds);
            }

            return hasMorePages;
        }

        #endregion

        /// <summary>
        /// Prepare a set of useful information which can be used by <see cref="IBlock"/> defined object 
        /// (footer, header, title)
        /// </summary>
        protected virtual void PrepareSessionCodes()
        {
            sessionCodes = new Dictionary<TemplateCode, string>();
            sessionCodes[TemplateCode.ReportTitle] = (Title == null) ? "" : Title.Text;
            sessionCodes[TemplateCode.SheetsCount] = PartitionDrawer.PartitionCount.ToString(CultureInfo.CurrentCulture);
            sessionCodes[TemplateCode.Date] = DateTime.Now.ToShortDateString();
            sessionCodes[TemplateCode.Time] = DateTime.Now.ToShortTimeString();
        }

        /// <summary>
        /// Prepare the bounds of partitions.
        /// </summary>
        /// <param name="graphic">graphics used to draw</param>
        /// <param name="clipBounds">Bounds area of the report</param>
        /// <param name="firstLevel">
        /// Are bounds calculated for a <paramref name="firstLevel"/> partition?
        /// </param>
        /// <returns>
        /// Bounds of partitions
        /// </returns>
        /// <remarks>
        /// 	<paramref name="clipBounds"/> is used to fix bounds of each element to draw,
        /// </remarks>
        private RectangleF adjustPartitionBoundsWithMe(Graphics graphic, RectangleF clipBounds, bool firstLevel)
        {
            clipBounds = new RectangleF(clipBounds.Location, clipBounds.Size);

            clipBounds = adjustBoundsWithHeader(graphic, clipBounds);

            if (firstLevel)
                clipBounds = adjustBoundsWithTitle(graphic, clipBounds);
            clipBounds = adjustBoundsWithFooter(graphic, clipBounds);
            clipBounds = adjustBoundsWithPadding(clipBounds);


            return clipBounds;
        }

        /// <summary>
        /// Adjust the <paramref name="clipBounds"/> area with the <see cref="GridPadding"/> property
        /// </summary>
        /// <param name="clipBounds">Paint area
        /// </param>
        /// <returns>
        /// Adjusted paint area
        /// </returns>
        private RectangleF adjustBoundsWithPadding(RectangleF clipBounds)
        {
            if (GridPadding.All != 0)
            {
                clipBounds.X += GridPadding.Left;
                clipBounds.Y += GridPadding.Top;
                clipBounds.Width -= GridPadding.Left + GridPadding.Right;
                clipBounds.Height -= GridPadding.Top + GridPadding.Bottom;
            }
            return clipBounds;
        }

        /// <summary>
        /// Fix the Footer clip bounds if it is not null and
        /// Adjust the <paramref name="clipBounds"/> area with the calculated footer clip bounds area,
        /// </summary>
        /// <param name="graphic">graphics used to draw</param>
        /// <param name="clipBounds">Paint area
        /// </param>
        /// <returns>
        /// Adjusted paint area
        /// </returns>
        private RectangleF adjustBoundsWithFooter(Graphics graphic, RectangleF clipBounds)
        {
            if (Footer != null)
            {
                float height = Footer.GetSize(graphic, clipBounds.Size).Height;
                clipBounds.Height -= height + Footer.Padding.Top + Footer.Padding.Bottom;
            }
            return clipBounds;
        }

        /// <summary>
        /// Fix the Title clip bounds if it is not null and
        /// Adjust the <paramref name="clipBounds"/> area with the calculated title clip bounds area,
        /// </summary>
        /// <param name="graphic">graphics used to draw</param>
        /// <param name="clipBounds">Paint area
        /// </param>
        /// <returns>
        /// Adjusted paint area
        /// </returns>
        private RectangleF adjustBoundsWithTitle(Graphics graphic, RectangleF clipBounds)
        {
            if (Title != null)
            {
                float height = Title.GetSize(graphic, clipBounds.Size).Height;

                clipBounds.Y += height;
                clipBounds.Height -= height;
            }
            return clipBounds;
        }

        /// <summary>
        /// Fix the Header clip bounds if it is not null and
        /// Adjust the <paramref name="clipBounds"/> area with the calculated header clip bounds area,
        /// </summary>
        /// <param name="graphic">graphics used to draw</param>
        /// <param name="clipBounds">
        /// </param>
        /// <returns>
        /// </returns>
        private RectangleF adjustBoundsWithHeader(Graphics graphic, RectangleF clipBounds)
        {
            if (Header != null)
            {
                float height = Header.GetSize(graphic, clipBounds.Size).Height;

                clipBounds.Y += height;
                clipBounds.Height -= height;
            }
            return clipBounds;
        }
    }
}